/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/camera.hh"
#include "carina/math.hh"
#include "carina/common/exception.hh"

namespace Carina
{
Camera::Camera(const RendererPtr& renderer, float n, float f, unsigned w, unsigned h)
    :   m_Renderer(renderer),
        m_NearPlane(n),
        m_FarPlane(f),
        m_Height(h),
        m_Width(w),
        m_Position(0.0f, 0.0f, 0.0f),
        m_Yaw(0.0f),
        m_Pitch(0.0f)
{
    rotate(m_Yaw, m_Pitch);

    _resize();
}

Camera::~Camera()
{
}

void Camera::setPosition(const Vector3& vec)
{
    m_Position = vec;
    ViewMatrix.identity();
    ViewMatrix.rotateX(m_Pitch);
    ViewMatrix.rotateY(m_Yaw);
    ViewMatrix.translate(-m_Position);
    TransformMatrix = ProjectionMatrix * ViewMatrix;
    InvTransformMatrix = TransformMatrix.inverse();
    _updateFrustum();
}

void Camera::translate(const Vector3& vec)
{
    ViewMatrix.translate(-vec);
    m_Position += vec;
    TransformMatrix = ProjectionMatrix * ViewMatrix;
    InvTransformMatrix = TransformMatrix.inverse();
    _updateFrustum();
}

void Camera::rotate(float yaw, float pitch)
{
    m_Yaw += (yaw / 180) * math_pi;
    m_Pitch += (pitch / 180) * math_pi;
    ViewMatrix.identity();
    ViewMatrix.rotateX(m_Pitch);
    ViewMatrix.rotateY(m_Yaw);
    ViewMatrix.translate(-m_Position);
    TransformMatrix = ProjectionMatrix * ViewMatrix;
    InvTransformMatrix = TransformMatrix.inverse();

    m_ForwardVector.x() = ViewMatrix[0][2];
    m_ForwardVector.y() = ViewMatrix[1][2];
    m_ForwardVector.z() = ViewMatrix[2][2];
    m_LeftVector.x() = ViewMatrix[0][0];
    m_LeftVector.y() = ViewMatrix[1][0];
    m_LeftVector.z() = ViewMatrix[2][0];
    _updateFrustum();
}

void Camera::_resize() {
    m_Renderer->resizeViewport(m_Width, m_Height);
    ProjectionMatrix = m_Renderer->createPerspectiveMatrix(54.0f, (float)m_Width/m_Height, m_NearPlane, m_FarPlane);
    TransformMatrix = ProjectionMatrix * ViewMatrix;
    InvTransformMatrix = TransformMatrix.inverse();
    _updateFrustum();
}

void Camera::_updateFrustum() { 
    /* Extract the numbers for the RIGHT plane */
    m_Frustum[0].x() = TransformMatrix[0][3] - TransformMatrix[0][0];
    m_Frustum[0].y() = TransformMatrix[1][3] - TransformMatrix[1][0];
    m_Frustum[0].z() = TransformMatrix[2][3] - TransformMatrix[2][0];
    m_Frustum[0].w() = TransformMatrix[3][3] - TransformMatrix[3][0];
    m_Frustum[0].normalizePartial();

    /* Extract the numbers for the LEFT plane */
    m_Frustum[1].x() = TransformMatrix[0][3] + TransformMatrix[0][0];
    m_Frustum[1].y() = TransformMatrix[1][3] + TransformMatrix[1][0];
    m_Frustum[1].z() = TransformMatrix[2][3] + TransformMatrix[2][0];
    m_Frustum[1].w() = TransformMatrix[3][3] + TransformMatrix[3][0];
    m_Frustum[1].normalizePartial();

    /* Extract the BOTTOM plane */
    m_Frustum[2].x() = TransformMatrix[0][3] + TransformMatrix[0][1];
    m_Frustum[2].y() = TransformMatrix[1][3] + TransformMatrix[1][1];
    m_Frustum[2].z() = TransformMatrix[2][3] + TransformMatrix[2][1];
    m_Frustum[2].w() = TransformMatrix[3][3] + TransformMatrix[3][1];
    m_Frustum[2].normalizePartial();

    /* Extract the TOP plane */
    m_Frustum[3].x() = TransformMatrix[0][3] - TransformMatrix[0][1];
    m_Frustum[3].y() = TransformMatrix[1][3] - TransformMatrix[1][1];
    m_Frustum[3].z() = TransformMatrix[2][3] - TransformMatrix[2][1];
    m_Frustum[3].w() = TransformMatrix[3][3] - TransformMatrix[3][1];
    m_Frustum[3].normalizePartial();

    /* Extract the FAR plane */
    m_Frustum[4].x() = TransformMatrix[0][3] - TransformMatrix[0][2];
    m_Frustum[4].y() = TransformMatrix[1][3] - TransformMatrix[1][2];
    m_Frustum[4].z() = TransformMatrix[2][3] - TransformMatrix[2][2];
    m_Frustum[4].w() = TransformMatrix[3][3] - TransformMatrix[3][2];
    m_Frustum[4].normalizePartial();

    /* Extract the NEAR plane */
    m_Frustum[5].x() = TransformMatrix[0][3] + TransformMatrix[0][2];
    m_Frustum[5].y() = TransformMatrix[1][3] + TransformMatrix[1][2];
    m_Frustum[5].z() = TransformMatrix[2][3] + TransformMatrix[2][2];
    m_Frustum[5].w() = TransformMatrix[3][3] + TransformMatrix[3][2];
    m_Frustum[5].normalizePartial();
}
}
